//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>

#include <buola/io/csocket.h>
#include <buola/io/cfdpoller.h>

namespace buola { namespace io {

static ESocketType deduce_socket_type(int pFD)
{
    int lType;
    socklen_t lLen=sizeof(lType);
    
    if(getsockopt(pFD,SOL_SOCKET,SO_TYPE,&lType,&lLen)!=0)
    {
        throw XInternal("can't deduce socket type");
    }

    switch(lType)
    {
        case SOCK_STREAM:
        case SOCK_DGRAM:
            return ESocketType::NONE;
        case SOCK_SEQPACKET:
            return ESocketType::PACKET;
        default:
            msg_warn() << "unknown type in DeduceSocketType!\n";
            return ESocketType::NONE;
    }
}

std::pair<PSocket,PSocket> socket_pair(ESocketType pType)
{
    int lFDs[2];
#if BUOLA_PLATFORM_MAC
    pType&=~ESocketType::PACKET;
#endif
    
    int lType=SOCK_STREAM;
    if(pType&ESocketType::PACKET)
        lType=SOCK_SEQPACKET;
    
    if(socketpair(AF_UNIX,lType,0,lFDs)!=0)
        throw XIO("can't create socket");

    return {new CSocket(nFromFD,lFDs[0],pType),new CSocket(nFromFD,lFDs[1],pType)};
}

ssize_t CSocket::ReceivePacketWithSocket(void *pDest,int pLen,PSocket &pSocket,int pFlags)
{
    pSocket.reset();

    struct msghdr lMsg;
    struct iovec lIOV[1];

    union
    {
        struct cmsghdr mCM;
        char mControl[CMSG_SPACE(sizeof(int))];
    } lControl;
    
    lMsg.msg_control=lControl.mControl;
    lMsg.msg_controllen=sizeof(lControl.mControl);
    
    lMsg.msg_name=nullptr;
    lMsg.msg_namelen=0;
    
    lIOV[0].iov_base=pDest;
    lIOV[0].iov_len=pLen;
    
    lMsg.msg_iov=lIOV;
    lMsg.msg_iovlen=1;
    
    ssize_t lRet=::recvmsg(FD(),&lMsg,pFlags);
    
    if(lRet<0)
    {
        if(errno==EAGAIN)
            return -1;
        else if(errno==EINTR)
            return ReceivePacketWithSocket(pDest,pLen,pSocket,pFlags);
        else if(errno==ECONNRESET)
            return 0;
        
        throw XIO("error in recvmsg: "+c_error_string(errno));
    }

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
    struct cmsghdr *lHdr=CMSG_FIRSTHDR(&lMsg);
#pragma GCC diagnostic pop

    if(lHdr&&lHdr->cmsg_len==CMSG_LEN(sizeof(int)))
    {
        unsigned char *lMsgData=CMSG_DATA(lHdr);
        int* lFD=(int*)lMsgData;
        pSocket.reset(new CSocket(nFromFD,*lFD,deduce_socket_type(*lFD)));
    }

    return lRet;
}

ssize_t CSocket::SendPacketWithSocket(const void *pSrc,int pLen,PSocket pSocket)
{
    struct msghdr lMsg;
    struct iovec lIOV[1];

    union
    {
        struct cmsghdr mCM;
        char mControl[CMSG_SPACE(sizeof(int))];
    } lControl;

    lMsg.msg_control=lControl.mControl;
    lMsg.msg_controllen=sizeof(lControl.mControl);

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
    struct cmsghdr *lHdr=CMSG_FIRSTHDR(&lMsg);
#pragma GCC diagnostic pop

    lHdr->cmsg_len=CMSG_LEN(sizeof(int));
    lHdr->cmsg_level=SOL_SOCKET;
    lHdr->cmsg_type=SCM_RIGHTS;
    unsigned char *lMsgData=CMSG_DATA(lHdr);
    int *lMsgFD=(int*)lMsgData;
    *lMsgFD=pSocket->FD();

    lMsg.msg_name=nullptr;
    lMsg.msg_namelen=0;

    lIOV[0].iov_base=const_cast<void*>(pSrc);
    lIOV[0].iov_len=pLen;

    lMsg.msg_iov=lIOV;
    lMsg.msg_iovlen=1;

#if BUOLA_PLATFORM_LINUX
    ssize_t lRet=::sendmsg(FD(),&lMsg,MSG_NOSIGNAL);
#else //BUOLA_PLATFORM_LINUX
    ssize_t lRet=::sendmsg(FD(),&lMsg,0);
#endif
    
    if(lRet<0)
    {
        if(errno==EAGAIN)
            return -1;
//            else if(errno==EINTR)
//                return Send(pSrc,pLen,pFlags);
        
        throw XIO("error in send_with_socket: "+c_error_string(errno));
    }
    
    return lRet;
}

/*namespace io*/ } /*namespace buola*/ }
